
#include <stdio.h>
#include <stdlib.h>
#include "acquire_client.h"

// For the command line options
#include <apr_general.h>
#include <apr_getopt.h>
#include <apr_strings.h>
#include <apr_signal.h>
#include "iface/APRInterface.h"

// For the password prompt
#include "db/KeyDB.h"


/** TESTING TESTING TESTING */
#include "utils/StreamFromDirectory.h"
#include "utils/StreamToDirectory.h"
/** TESTING TESTING TESTING */

typedef struct client_args
{
   char *brokername;
   char *hostname;
   char *hostport;
   char *username;
   char *xmlfile;
   char *directory;
   char *sessionid;
   char *downloadid;
   char *remotemachine;
   int instances;
   int do_addtopool;
   int do_keyupdate;
   int do_register;
   int do_create;
   int do_reserve;
   int do_upload;
   int do_queryuser;
   int do_querysession;
   int do_querydownloadid;
   int do_clusterlist;
   int do_download;
   int do_delete;
   int do_test;
} ClientArgs;

int main(int argc, const char *argv[])
{
   int nargc = argc;
   const char *const *nargv = argv;
   AcquireClientInitLibraries();
   static const apr_getopt_option_t opt_option[] = {
      { "broker", 'b', TRUE, "internal name of the broker to connect to (broker)" },
      { "host", 'n', TRUE, "hostname to connect to (broker host)" },
      { "port", 'p', TRUE, "port to connect to (broker port)" },
      { "user", 'u', TRUE, "username" },
      { "directory", 'd', TRUE, "directory to run work in" },
      { "xmldescriptor", 'x', TRUE, "xml description of work under scrutiny" },
      { "sessionid", 's', TRUE, "session id (hexadecimal uuid)" },
      { "downloadid", 'l', TRUE, "download id (hexadecimal uuid)" },
      { "remotemachine", 'm', TRUE, "remote machine SSH name (like user@locahost)" },
      { "instances", 'i', TRUE, "number of instances to spawn" },
      { "addtopool", 'A', FALSE, "add host to SSH pool" },
      { "keyupdate", 'K', FALSE, "do key update with broker" },
      { "register", 'R', FALSE, "do user registration with broker" },
      { "create", 'C', FALSE, "create initial xml descriptor" },
      { "reserve", 'V', FALSE, "reserve space and obtain upload permission" },
      { "delete", 'X', FALSE, "delete (expunge) session" },
      { "upload", 'U', FALSE, "upload work, begin process on completion" },
      { "queryuser", 'Q', FALSE, "query status of work started by user" },
      { "querysession", 'E', FALSE, "query status of work session" },
      { "querydownloadid", 'I', FALSE, "query the download handles of completed work" },
      { "listclusters", 'L', FALSE, "list accessible clusters" },
      { "download", 'D', FALSE, "download work" },
      { "test", 'T', FALSE, "test" },
      { "help", 'h', FALSE, "show help" },
      { NULL, 0, 0, NULL } };
   apr_signal_block(SIGPIPE);
   apr_getopt_t *opt;
   int optch;
   const char *optarg;
   apr_status_t status;
   apr_pool_t *mp = NULL;
   status = apr_pool_create(&mp, NULL);
   CheckAPRError(status);
   ClientArgs args;
   args.brokername = apr_pstrdup(mp, "broker");
   args.hostname = apr_pstrdup(mp, DEFAULT_HOST);
   args.hostport = apr_pstrdup(mp, DEFAULT_PORT_STRING);
   args.username = apr_pstrdup(mp, "");
   args.xmlfile = apr_pstrdup(mp, "work0001.xml");
   args.directory = apr_pstrdup(mp, "/dev/null");
   args.instances = 1;
   args.sessionid = NULL;
   args.downloadid = NULL;
   args.remotemachine = NULL;
   args.do_addtopool = 0;
   args.do_keyupdate = 0;
   args.do_register = 0;
   args.do_create = 0;
   args.do_reserve = 0;
   args.do_delete = 0;
   args.do_upload = 0;
   args.do_queryuser = 0;
   args.do_querysession = 0;
   args.do_querydownloadid = 0;
   args.do_clusterlist = 0;
   args.do_download = 0;
   args.do_test = 0;
   apr_getopt_init(&opt, mp, nargc, nargv);
   while ((status = apr_getopt_long(opt, opt_option, &optch, &optarg)) == APR_SUCCESS)
   {
      switch (optch)
      {
         case 'b': args.brokername = apr_pstrdup(mp, optarg); break;
         case 'n': args.hostname = apr_pstrdup(mp, optarg); break;
         case 'p': args.hostport = apr_pstrdup(mp, optarg); break;
         case 'u': args.username = apr_pstrdup(mp, optarg); break;
         case 'x': args.xmlfile = apr_pstrdup(mp, optarg); break;
         case 'd': args.directory = apr_pstrdup(mp, optarg); break;
         case 's': args.sessionid = apr_pstrdup(mp, optarg); break;
         case 'l': args.downloadid = apr_pstrdup(mp, optarg); break;
         case 'm': args.remotemachine = apr_pstrdup(mp, optarg); break;
         case 'i': args.instances = atoi(optarg); break;
         case 'A': args.do_addtopool = 1; break;
         case 'K': args.do_keyupdate = 1; break;
         case 'R': args.do_register = 1; break;
         case 'C': args.do_create = 1; break;
         case 'V': args.do_reserve = 1; break;
         case 'X': args.do_delete = 1; break;
         case 'U': args.do_upload = 1; break;
         case 'Q': args.do_queryuser = 1; break;
         case 'E': args.do_querysession = 1; break;
         case 'I': args.do_querydownloadid = 1; break;
         case 'L': args.do_clusterlist = 1; break;
         case 'D': args.do_download = 1; break;
         case 'T': args.do_test = 1; break;
         default:
         {
            const apr_getopt_option_t *options = opt_option;
            int count = 0;
            printf("Help for client program:\n");
            printf("   progname");
            while (options[count].optch != '\0')
            {
               printf(" -%c", options[count].optch);
               if (options[count].has_arg)
                  printf(" <%s>", options[count].name);
               count++;
            }
            printf("\n\n");
            count = 0;
            while (options[count].optch != '\0')
            {
               printf("(-%c or --%s) - %s\n",
                  options[count].optch, options[count].name, options[count].description);
               count++;
            }
            return 0;
            break;
         }
      }
   }
   if (status != APR_EOF)
      CheckAPRError(status);
   const char *mypassword = KeyDB::AskPassword("Password: ");
   if (!(AcquireClientInitState(args.hostname, atoi(args.hostport), args.username, mypassword, args.brokername)))
   {
      printf("Error encountered while setting initial client state\n");
      return 1;
   }
   if (args.do_addtopool)
   {
      // Need to make this StringUtils - so we can get rid of KeyDB
      char *sshpass = KeyDB::AskPasswordEx("Password: ");
      char *csl_failedhosts = NULL;
      int result = AcquireAddSSHHostToPool(args.remotemachine, sshpass, &csl_failedhosts);
      if (sshpass)
      {
         memset(sshpass, 0, sizeof(char)*strlen(sshpass));
         free(sshpass);
      }
      switch (result)
      {
         case -1: printf("Unknown result\n"); break;
         case 0: printf("Missing information or access denied\n"); break;
         case ACQUIRE_USER_ADD_SSH__SSH_SUCCESS: printf("Successfully added SSH host\n"); break;
         case ACQUIRE_USER_ADD_SSH__FINGERPRINT_UNTRUSTED:
         {
            if (csl_failedhosts)
               printf("Routing to node failed, tried to route through hosts '%s', some failures were due to untrusted fingerprints (contact administrator)\n", csl_failedhosts);
            else
               printf("Routing to node failed, fingerprint flag set (but no hosts returned)\n");
            break;
         }
         case ACQUIRE_USER_ADD_SSH__SSH_ROUTING_FAILED:
         {
            if (csl_failedhosts)
               printf("Routing to node failed, tried to route through hosts '%s'\n", csl_failedhosts);
            else
               printf("No route to host or untrusted host specified\n");
            break;
         }
      }
   }
   if (args.do_keyupdate)
   {
      int result = AcquireUpdateKeys();
      if (result)
         printf("Key updates successful\n");
      else
         printf("Key update failed\n");
   }
   if (args.do_register)
   {
      int result = AcquireRegisterUserWithBroker();
      switch (result)
      {
         case -1: printf("Unknown result\n"); break;
         case 0: printf("Missing information or access denied\n"); break;
         case ACQUIRE_USER_REGISTER__USER_IS_AUTHORISED: printf("User account is active\n"); break;
         case ACQUIRE_USER_REGISTER__IS_ACCEPTED_FOR_REVIEW: printf("This registration has been accepted subject to review by adminstrators\n"); break;
         case ACQUIRE_USER_REGISTER__NAME_IS_INVALID: printf("This username is invalid, or already in use\n"); break;
         case ACQUIRE_USER_REGISTER__USER_PENDING_REVIEW: printf("Registration is awaiting or under review by administrators\n"); break;
      }
   }
   if (args.do_create)
   {
      int result = AcquireGenerateInitialXMLWorkDescriptor(args.xmlfile, "somework", "work/morework/example", "single", 60*4);
      if (result)
         printf("Wrote initial work descriptor to file\n");
      else
         printf("Unable to write initial work descriptor to file\n");
   }
   if (args.do_reserve)
   {
      int blocks = 0;
      int64_t space = 0;
      const char *store_id = AcquireReserveWorkStore(args.xmlfile, args.directory, &space, &blocks, NULL);
      if (store_id)
      {
         printf("Session has been started:\n");
         printf("WORKSTOREID %s\n", store_id);
         printf("this uses %ld bytes of space", space);
      } else printf("Failed to reserve a file store on the broker for this work\n");
   }
   if (args.do_delete)
   {
      int result = AcquireDeleteWorkStore(args.sessionid);
      switch (result)
      {
         case -1: printf("Unknown result\n"); break;
         case 0: printf("Session does not exist or access denied\n"); break;
         case 1: printf("Session deleted successfully\n"); break;
      }
   }
   if (args.do_upload)
   {
      int blocksdone = 0;
      int result = AcquireUploadToWorkStore(args.sessionid, args.directory, NULL, "hello", args.instances, 20, &blocksdone, NULL);
      if (result)
         printf("Successfully uploaded directory to the broker\n");
      else
         printf("Upload failed\n");
   }
   if (args.do_download)
   {
      int blocksdone = 0;
      int blocksexpected = 0;
      int result = AcquireDownloadFromWorkStore(args.sessionid, args.downloadid, args.directory, &blocksdone, &blocksexpected, NULL);
      if (result)
         printf("Successfully downloaded to the directory from the broker\n");
      else
         printf("Download failed\n");
   }
   if (args.do_queryuser)
   {
      char **store_id_list;
      float *transfers_b2c;
      float *compute;
      float *transfers_c2b;
      int numberofids;
      int64_t quota = 0;
      int result = AcquireQueryAllWorkStatus(&store_id_list, &transfers_b2c, &compute, &transfers_c2b, &numberofids, &quota);
      switch (result)
      {
         case -2: printf("Broken: too many lines\n"); break;
         case -1: printf("Unknown result\n"); break;
         case 0: printf("Access denied\n"); break;
         case 1:
         {
            printf("Query completed successfully\n");
            for (int i = 0; i < numberofids; i++)
               printf("SESSION %s:\t1: %02.1f%%\t2: %02.1f%%\t3: %02.1f%%\n", store_id_list[i], transfers_b2c[i], compute[i], transfers_c2b[i]);
            break;
         }
         case 2: printf("No sessions found\n"); break;
      }
   }
   if (args.do_querysession)
   {
      // Only 1 work instance!
      int resultsarray[1];
      int result = AcquireAreResultsFromWorkStoreReady(args.instances, resultsarray, args.sessionid);
      switch (result)
      {
         case -2: printf("Access denied\n"); break;
         case -1: printf("Unknown result\n"); break;
         case 0: printf("Results for this work store are not ready\n"); break;
         case 1:
         {
            printf("There are results waiting for download from this work store\n");
            printf("Results are ready from the following instances:\n");
            for (int k = 0; k < 1; k++)
            {
               if (resultsarray[k]) printf("%d, ", k);
            }
            printf("\n");
            break;
         }
      }
   }
   if (args.do_querydownloadid)
   {
      // Assuming 1 work instance, so can only be index 0...
      const char *downloadable_id = AcquireGetResultIDFromNumberWhenReady(args.instances - 1, args.sessionid);
      if (downloadable_id)
      {
         printf("SESSION %s, instance %d can be downloaded as:\n", args.sessionid, args.instances - 1);
         printf("DOWNLOADID %s\n", downloadable_id);
      } else
      {
         printf("Access denied or this work is unfinished\n");
      }
   }
   if (args.do_clusterlist)
   {
      const char *clusterlist = AcquireListOfAccessibleClusters();
      if (clusterlist)
      {
         printf("CLUSTERS AVAILABLE %s\n", clusterlist);
      } else
      {
         printf("Access denied or no clusters available\n");
      }
   }
#define TESTSIZE 4096
   if (args.do_test)
   {
      StreamFromDirectory *sfd = new StreamFromDirectory("/home/chaders/thesis", mp);
      StreamToDirectory *std = new StreamToDirectory("/home/chaders/faked_thesis", mp);
      char dblock[TESTSIZE];
      int rem = sfd->getNextBlock(dblock, TESTSIZE);
         printf("Triggeredq %d!\n", rem);
      while (rem == TESTSIZE)
      {
         printf("Triggered %d!\n", rem);
         int written = std->putNextBlock(dblock, rem);
         rem = sfd->getNextBlock(dblock, TESTSIZE);
         if (written != rem) printf("argghhh!\n");
      }
      int written2 = std->putNextBlock(dblock, rem);
      delete std;
      delete sfd;
   }
   apr_pool_destroy(mp);
   AcquireClientClearResults();
   AcquireClientDestroy();
   memset((void *)mypassword, 0, sizeof(char)*(strlen(mypassword)));
   free((void *)mypassword);
   printf("Client shutdown.\n");
   return 0;
}
